#ifndef QBINARYRPC_STATICS_H
#define QBINARYRPC_STATICS_H

#ifndef ntohl
#define ntohl(A) ((((A) &0xff000000) >> 24) | \
                 (((A) &0x00ff0000) >> 8) | \
                 (((A) &0x0000ff00) << 8) | \
                 (((A) &0x000000ff) << 24))
#endif

#include "qvariant.h"
#include "qdatetime.h"

namespace qbinaryrpc {

class Statics {
public:
  static void encodeChar(char input_value, QByteArray &output_array) {
    output_array.push_back(input_value);
  }
  static void encodeInt32(qint32 input_value, QByteArray &output_array) {
    qint32 temp(ntohl(input_value));
    output_array += QByteArray::fromRawData(
        reinterpret_cast<char *>(&temp), sizeof(qint32));
  }
  static void encodeUInt32(quint32 input_value, QByteArray &output_array) {
    quint32 temp(ntohl(input_value));
    output_array += QByteArray::fromRawData(
        reinterpret_cast<char *>(&temp), sizeof(quint32));
  }
  static void encodeInt64(qint64 input_value, QByteArray &output_array) {
    output_array += QByteArray::fromRawData(
        reinterpret_cast<char *>(&input_value), sizeof(qint64));
  }
  static void encodeUInt64(quint64 input_value, QByteArray &output_array) {
    output_array += QByteArray::fromRawData(
        reinterpret_cast<char *>(&input_value), sizeof(quint64));
  }
  static void encodeDouble(double input_value, QByteArray &output_array) {
    output_array += QByteArray::fromRawData(
        reinterpret_cast<char *>(&input_value), sizeof(double));
  }
  static void encodeString(const QString &input_value,
                           QByteArray &output_array) {
    output_array += input_value + char('\0');
  }
  static void encodeByteArray(const QByteArray &input_value,
                              QByteArray &output_array) {
      output_array += input_value;
  }
  static bool encodeNullValue(const QVariant &input_value,
                              QByteArray &output_array) {
    if(input_value.type() == QVariant::Invalid) {
      encodeChar(0x00, output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeBoolValue(const QVariant &input_value,
                              QByteArray &output_array) {
    if(input_value.type() == QVariant::Bool) {
      encodeChar(0x01, output_array);
      encodeChar(char(input_value.toBool()), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeCharValue(const QVariant &input_value,
                              QByteArray &output_array) {
    if(input_value.type() == QVariant::Char) {
      encodeChar(0x02, output_array);
      encodeChar(input_value.toChar().toLatin1(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeInt32Value(const QVariant &input_value,
                               QByteArray &output_array) {
    if(input_value.type() == QVariant::Int) {
      encodeChar(0x03, output_array);
      encodeInt32(input_value.toInt(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeUInt32Value(const QVariant &input_value,
                                QByteArray &output_array) {
    if(input_value.type() == QVariant::UInt) {
      encodeChar(0x04, output_array);
      encodeUInt32(input_value.toUInt(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeInt64Value(const QVariant &input_value,
                               QByteArray &output_array) {
    if(input_value.type() == QVariant::LongLong) {
      encodeChar(0x05, output_array);
      encodeInt64(input_value.toLongLong(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeUInt64Value(const QVariant &input_value,
    QByteArray &output_array) {
      if(input_value.type() == QVariant::ULongLong) {
        encodeChar(0x06, output_array);
        encodeUInt64(input_value.toULongLong(), output_array);
        return true;
      } else {
        return false;
      }
  }
  static bool encodeDoubleValue(const QVariant &input_value,
                                QByteArray &output_array) {
    if(input_value.type() == QVariant::Double) {
      encodeChar(0x07, output_array);
      encodeDouble(input_value.toDouble(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeStringValue(const QVariant &input_value,
                                QByteArray &output_array) {
    if(input_value.type() == QVariant::String) {
      encodeChar(0x08, output_array);
      encodeString(input_value.toString(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeByteArrayValue(const QVariant &input_value,
                                   QByteArray &output_array) {
    if(input_value.type() == QVariant::ByteArray) {
      encodeChar(0x09, output_array);
      encodeInt32(input_value.toByteArray().size(), output_array);
      encodeByteArray(input_value.toByteArray(), output_array);
      return true;
    } else {
      return false;
    }
  }
  static bool encodeDateTimeIso8601Value(const QVariant &input_value,
                                    QByteArray &output_array) {
    if(input_value.type() != QVariant::DateTime) return false;
    QDateTime date_time(input_value.toDateTime());
    bool is_valid(date_time.isValid());
    if(is_valid) {
      encodeChar(0x0A, output_array);
      encodeString(date_time.toString("yyyyMMddThh:mm:ss"), output_array);
    }
    return is_valid;
  }
  static bool encodeStructValue(const QVariant &input_value,
                                QByteArray &output_array) {
    if(input_value.type() != QVariant::Map) return false;
    QVariantMap input_value_map(input_value.toMap());
    QByteArray temp;
    encodeChar(0x0B, temp);
    int struct_size(input_value_map.size());
    encodeInt32(struct_size, temp);
    if(struct_size > 0) {
      QVariantMap::iterator input_value_map_iterator(
          input_value_map.begin());
      for (int i = 0;
           i < struct_size;
           i++, input_value_map_iterator++) {
        if(input_value_map_iterator.key().isEmpty()) return false;
        encodeString(input_value_map_iterator.key(), temp);
        if(encodeValue(input_value_map_iterator.value(), temp) == false)
          return false;
      }
    }
    output_array += temp;
    return true;
  }
  static bool encodeArrayValue(const QVariant &input_value,
                               QByteArray &output_array) {
    if(input_value.type() != QVariant::List) return false;
    QVariantList input_value_list(input_value.toList());
    QByteArray temp;
    encodeChar(0x0C, temp);
    encodeInt32(input_value_list.size(), temp);
    foreach (QVariant value, input_value_list)
      if(encodeValue(value, temp) == false) return false;
    output_array += temp;
    return true;
  }
  static bool encodeValue(const QVariant &input_value,
                          QByteArray &output_array) {
    if(encodeNullValue(input_value, output_array))
      return true;
    else if(encodeBoolValue(input_value, output_array))
      return true;
    else if(encodeCharValue(input_value, output_array))
      return true;
    else if(encodeInt32Value(input_value, output_array))
      return true;
    else if(encodeUInt32Value(input_value, output_array))
      return true;
    else if(encodeInt64Value(input_value, output_array))
      return true;
    else if(encodeUInt64Value(input_value, output_array))
      return true;
    else if(encodeDoubleValue(input_value, output_array))
      return true;
    else if(encodeStringValue(input_value, output_array))
      return true;
    else if(encodeByteArrayValue(input_value, output_array))
      return true;
    else if(encodeDateTimeIso8601Value(input_value, output_array))
      return true;
    else if(encodeStructValue(input_value, output_array))
      return true;
    else if(encodeArrayValue(input_value, output_array))
      return true;
    else
      return false;
  }
  static bool decodeChar(const QByteArray &input_value,
                         int max_position,
                         int &now_position,
                         char &output_char) {
    if(now_position > max_position ||
       now_position + 1 > input_value.size()) return false;
    output_char = input_value[now_position++];
    return true;
  }
  static bool decodeInt32(const QByteArray &input_value,
                          int max_position,
                          int &now_position,
                          qint32 &output_int32) {
    int int32_size(sizeof(qint32));
    if(now_position + int32_size - 1 > max_position ||
       now_position + int32_size > input_value.size()) return false;
    memcpy(&output_int32, &input_value.data()[now_position], int32_size);
    output_int32 = ntohl(output_int32);
    now_position += int32_size;
    return true;
  }
  static bool decodeUInt32(const QByteArray &input_value,
                           int max_position,
                           int &now_position,
                           quint32 &output_uint32) {
    int uint32_size(sizeof(quint32));
    if(now_position + uint32_size - 1 > max_position ||
       now_position + uint32_size > input_value.size()) return false;
    memcpy(&output_uint32, &input_value.data()[now_position], uint32_size);
    output_uint32 = ntohl(output_uint32);
    now_position += uint32_size;
    return true;
  }
  static bool decodeInt64(const QByteArray &input_value,
                          int max_position,
                          int &now_position,
                          qint64 &output_int64) {
    int int64_size(sizeof(qint64));
    if(now_position + int64_size - 1 > max_position ||
       now_position + int64_size > input_value.size()) return false;
    memcpy(&output_int64, &input_value.data()[now_position], int64_size);
    now_position += int64_size;
    return true;
  }
  static bool decodeUInt64(const QByteArray &input_value,
                           int max_position,
                           int &now_position,
                           quint64 &output_uint64) {
    int uint64_size(sizeof(quint64));
    if(now_position + uint64_size - 1 > max_position ||
       now_position + uint64_size > input_value.size()) return false;
    memcpy(&output_uint64, &input_value.data()[now_position], uint64_size);
    now_position += uint64_size;
    return true;
  }
  static bool decodeDouble(const QByteArray &input_value,
                           int max_position,
                           int &now_position,
                           double &output_double) {
    int double_size(sizeof(double));
    if(now_position + double_size - 1 > max_position ||
       now_position + double_size > input_value.size()) return false;
    memcpy(&output_double, &input_value.data()[now_position], double_size);
    now_position += double_size;
    return true;
  }
  static bool decodeString(const QByteArray &input_value,
                           int max_position,
                           int &now_position,
                           QString &output_string) {
    int string_size(now_position), input_value_size(input_value.size());
    while(1) {
      if(string_size > max_position ||
         string_size + 1 > input_value_size) return false;
      if(input_value[string_size++] == '\0') break;
    }
    string_size -= now_position + 1;
    output_string = input_value.mid(now_position, string_size);
    now_position += string_size + 1;
    return true; 
  }
  static bool decodeByteArray(const QByteArray &input_value,
                              int max_position,
                              int &now_position,
                              int size_massive,
                              QByteArray &output_char) {
    if(now_position + size_massive - 1 > max_position ||
       now_position + size_massive > input_value.size()) return false;
    output_char = input_value.mid(now_position, size_massive);
    now_position += size_massive;
    return true;
  }
  static bool decodeDateTimeIso8601(const QString &string_date_time,
                                    QDateTime *output_value = 0) {
    QDateTime date_time(QDateTime::fromString(
                            string_date_time, Qt::ISODate));
    if(date_time.isValid()) {
      if(output_value) *output_value = date_time;
      return true;
    } else {
      return false;
    }
  }
  static bool decodeValue(const QByteArray &input_value,
                          int max_position,
                          int &now_position,
                          QVariant *output_value = 0) {
    char type;
    if(decodeChar(input_value, max_position, now_position, type) == false)
      return false;
    switch(type) {
      case 0x01: {
        char temp;
        if(decodeChar(input_value,
                      max_position,
                      now_position,
                      temp) == false) return false;
        if(temp != 0x00 && temp != 0x01)
          return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x02: {
        char temp;
        if(decodeChar(input_value,
                      max_position,
                      now_position,
                      temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x03: {
        qint32 temp;
        if(decodeInt32(input_value, 
                       max_position, 
                       now_position, 
                       temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x04: {
        quint32 temp;
        if(decodeUInt32(input_value, 
                        max_position, 
                        now_position, 
                        temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x05: {
        qint64 temp;
        if(decodeInt64(input_value, 
                       max_position, 
                       now_position, 
                       temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x06: {
        quint64 temp;
        if(decodeUInt64(input_value, 
                        max_position, 
                        now_position, 
                        temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x07: {
        double temp;
        if(decodeDouble(input_value, 
                        max_position, 
                        now_position, 
                        temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x08: {
        QString temp;
        if(decodeString(input_value, 
                        max_position, 
                        now_position, 
                        temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x09: {
        int size_massive;
        if(decodeInt32(input_value, 
                       max_position, 
                       now_position, 
                       size_massive) == false) return false;
        QByteArray temp;
        if(decodeByteArray(input_value, 
                        max_position, 
                        now_position,
                        size_massive,
                        temp) == false) return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x0A: {
        QString string_date_time;
        if(decodeString(input_value, 
                        max_position, 
                        now_position, 
                        string_date_time) == false) return false;
        QDateTime temp;
        if(decodeDateTimeIso8601(string_date_time,&temp) == false)
          return false;
        if(output_value) *output_value = temp;
        return true;
      }
      case 0x0B: {
        return decodeStruct(input_value,
                            max_position,
                            now_position,
                            output_value);
      }
      case 0x0C: {
        return decodeArray(input_value,
                           max_position,
                           now_position,
                           output_value);
      }
      default: {
        return false;
      }
    }
  }

  static bool decodeStruct(const QByteArray &input_value,
                           int max_position,
                           int &now_position,
                           QVariant *output_value = 0) {
    int struct_size;
    if(decodeInt32(input_value, 
                   max_position, 
                   now_position, 
                   struct_size) == false) return false;
    QVariantMap value_map;
    for (int i = 0; i < struct_size; i++) {
      QString value_name;
      if(decodeString(input_value, 
                      max_position, 
                      now_position, 
                      value_name) == false) return false;
      if(value_name.isEmpty()) return false;
      QVariant value;
      if(decodeValue(input_value,
                     max_position,
                     now_position,
                     &value) == false) return false;
      value_map.insert(value_name,value);
    }
    if(output_value) *output_value = value_map;
    return true;
  }
  static bool decodeArray(const QByteArray &input_value,
                          int max_position,
                          int &now_position,
                          QVariant *output_value = 0) {
    int array_size;
    if(decodeInt32(input_value, 
                   max_position, 
                   now_position, 
                   array_size) == false) return false;
    QVariantList value_list;
    for (int i = 0; i < array_size; i++) {
      QVariant value;
      if(decodeValue(input_value,
                     max_position,
                     now_position,
                     &value) == false) return false;
      value_list.push_back(value);
    }
    if(output_value) *output_value = value_list;
    return true;
  }
};

} //namespace qbinaryrpc

#endif //QBINARYRPC_STATICS_H
